/*
package cn.springBoot.service.impl;

import cn.springBoot.dto.UserTaskDTO;
import cn.springBoot.service.ProcessService;
import cn.springBoot.util.DrawUtils;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.*;
import org.camunda.bpm.model.bpmn.instance.*;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

*/
/**
 * @author ljx
 * @description: TODO
 * @class cn.springBoot.service.impl.ProcessServiceImpl
 * @create date: 2021/10/9
 *//*

public class ProcessServiceImpl implements ProcessService {


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean modifyProcessDefinition(String processDefinitionName, List<Map<String, Object>> nodeList) throws Exception {
        // ProcessDefinition processDefinition = checkProcessDefinitionByName(processDefinitionName);这个方法是为了校验是否存在该流程定义，此处我将其注释
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionName(processDefinitionName)
                .latestVersion()
                .list();
        // ①获取节点信息（按顺序），这里是转换为dto，UserTaskDTO中的属性为：
        // private String userTaskId;
        // private String userTaskName;
        // private List<String> groupList;
        // private String type;
        // 我这里只写了这四个属性，map中当然也就是就这四个
        List<UserTaskDTO> list = nodeList.stream()
                .map(this::toUserTaskDTO)
                .collect(Collectors.toList());
        // 简单再校验一下流程结点的名称
        List<String> nameList = list.stream()
                .map(UserTaskDTO::getUserTaskName)
                .collect(Collectors.toList());
        if (nameList.size() != new HashSet<>(nameList).size()) {
            throw new RuntimeException("编辑流程结点名称不允许重名，请修改后保存");
        }
        //以上就是平凡无奇的一些操作了

        // ②准备工作，下面要进行一些对流程编写的准备工作，首先就是要创建一个BpmnModelInstance，bpmn模型实例，然后是在bpmn文件中的<definitions>这个标签
        BpmnModelInstance modelInstance = Bpmn.createEmptyModel();
        Definitions definitions = modelInstance.newInstance(Definitions.class);
        definitions.setTargetNamespace(BPMN20_NS);
        definitions.getDomElement().registerNamespace("camunda", CAMUNDA_NS);
        modelInstance.setDefinitions(definitions);
        // Process也是在bpmn文件中的一个标签（可以打开一个bpmn文件进行查看），节点
        Process process = DrawUtils.createElement(definitions, processDefinitionName, Process.class);
        // process的id就是processDefinitionKey，process的name就是processDefinitionName
        process.setId(processDefinition.getKey());
        process.setName(processDefinitionName);
        // 必须设置为可执行的,否则在流程定义表中无法生成流程定义
        process.setExecutable(true);
        // 接下来是两个必须具备的两个节点，也是准备工作之一
        StartEvent startEvent = DrawUtils.createElement(process, "start", StartEvent.class);
        EndEvent endEvent = DrawUtils.createElement(process, "end", EndEvent.class);

        // 这里新建了两个在我的流程里面不会重复的任务节点，一个是录入节点，一个是修改节点，在我的流程里都只会出现一次，我的所有的复核不通过，都会回到修改节点。
        UserTaskDTO inputDTO = null;
        UserTaskDTO modifyDTO = null;
        // 未知数量的复核节点
        List<UserTaskDTO> reviewList = new ArrayList<>();
        // 这里根据task的类型置入（具体实现可以更改）
        for (UserTaskDTO userTaskDTO : list) {
            if (userTaskDTO.getType().equals("复核")) {
                reviewList.add(userTaskDTO);
            }
            if (userTaskDTO.getType().equals("录入")) {
                inputDTO = userTaskDTO;
            }
            if (userTaskDTO.getType().equals("修改")) {
                modifyDTO = userTaskDTO;
            }
        }

        if (Objects.isNull(inputDTO)) {
            throw new RuntimeException("inputDTO不存在");
        }
        // 先删除所有，再添加保存（此处的功能自己实现吧，我这里写的是简略版，具体功能是根据流程定义name删除当前它所有的审批组）
        // CandidateGroupDbo中有四个属性：流程定义name，userTaskId,userTaskGroupId（某个节点的写死的审批组名称），groupList（这个节点的实际审批组列表）
        candidateGroupRepo.deleteAll();
        CandidateGroupDbo inputCandidateGroupDbo = new CandidateGroupDbo(
                processDefinition.getName(),
                inputDTO.getUserTaskId(),
                processDefinition.getName() + inputDTO.getUserTaskId(),
                inputDTO.getGroupList());
        UserTask inputNode = DrawUtils.createUserTask(
                process, inputDTO.getUserTaskId(), inputDTO.getUserTaskName(),
                inputCandidateGroupDbo.getProcessDefinitionName() + inputCandidateGroupDbo.getUserTaskId(),
                inputDTO.getType());
        candidateGroupRepo.save(inputCandidateGroupDbo);
        // 这个方法的实际功能是创建一个group，group的id就是流程定义name+usertaskId的合体，然后group的users就是groupList中所有group的user，这样之后就可以通过流程定义name+usertaskId的方式寻找所有的该节点的审批组及审批用户（不确定是否有更好的方法，此处先这样写）
        processIdentityService.addUsersToGroupByGroupList(processDefinition.getName() + inputDTO.getUserTaskId(), inputDTO.getGroupList());
        // start --> 录入
        DrawUtils.createSequenceFlow(process, startEvent, inputNode);

        if (Objects.isNull(modifyDTO)) {
            throw new RuntimeException("modifyDTO不存在");
        }
        // 获取修改节点
        CandidateGroupDbo modifyCandidateGroupDbo = new CandidateGroupDbo(
                processDefinition.getName(),
                modifyDTO.getUserTaskId(),
                processDefinition.getName() + modifyDTO.getUserTaskId(),
                modifyDTO.getGroupList());
        UserTask modifyNode = DrawUtils.createUserTask(
                process, modifyDTO.getUserTaskId(), modifyDTO.getUserTaskName(),
                modifyCandidateGroupDbo.getProcessDefinitionName() + modifyCandidateGroupDbo.getUserTaskId(),
                modifyDTO.getType());
        candidateGroupRepo.save(modifyCandidateGroupDbo);
        // 同上面input
        processIdentityService.addUsersToGroupByGroupList(processDefinition.getName() + modifyDTO.getUserTaskId(), modifyDTO.getGroupList());

        // 获取第一个复核结点，用于修改节点 -> 第一个复核结点
        boolean first = true;
        UserTask review1DTO = null;
        // 如果一个复核节点都没有，则抛出错误
        if (reviewList.isEmpty()) {
            throw new RuntimeException("不存在复核结点！");
        }
        for (UserTaskDTO u : reviewList) {
            CandidateGroupDbo candidateGroupDbo = new CandidateGroupDbo(
                    processDefinition.getName(),
                    u.getUserTaskId(),
                    processDefinition.getName() + u.getUserTaskId(),
                    u.getGroupList());
            UserTask userTask = DrawUtils.createUserTask(
                    process, u.getUserTaskId(), u.getUserTaskName(),
                    candidateGroupDbo.getProcessDefinitionName() + candidateGroupDbo.getUserTaskId(),
                    u.getType());
            candidateGroupRepo.save(candidateGroupDbo);
            processIdentityService.addUsersToGroupByGroupList(processDefinition.getName() + u.getUserTaskId(), u.getGroupList());
            // 只在第一次将录入与第一个复核节点连接起来
            if (first) {
                DrawUtils.createSequenceFlow(process, inputNode, userTask);
                review1DTO = userTask;
                first = false;
            }
            List<ExclusiveGateway> gateways = (List<ExclusiveGateway>) modelInstance.getModelElementsByType(ExclusiveGateway.class);
            // 如果有网关的话，将网关与当前的节点进行连线（这种连线都是根据实际情况，因为我的流程图就是一个节点后面跟着一个网关这样子，所以就直接这样写在for里面了，如果你们不是，那么根据实际需求进行改动）
            if (gateways.size() >= 1) {
                ExclusiveGateway last = gateways.get(gateways.size() - 1);
                SequenceFlow sequenceFlow = DrawUtils.createSequenceFlow(process, last, userTask);
                // 设置顺序流条件(复核通过)#和$好像都可以,很久以前写的了，有点忘了
                ConditionExpression conditionExpression = modelInstance.newInstance(ConditionExpression.class);
                conditionExpression.setTextContent("#{confirm==true}");
                sequenceFlow.setConditionExpression(conditionExpression);
            }
            ExclusiveGateway exclusiveGateway = DrawUtils.createElement(process, u.getUserTaskId() + "gateway", ExclusiveGateway.class);
            // 将节点指向新建的网关
            DrawUtils.createSequenceFlow(process, userTask, exclusiveGateway);
        }
        //获取最后一个网关，并指向end节点（此处是排他网关）
        List<ExclusiveGateway> gateways = (List<ExclusiveGateway>) modelInstance.getModelElementsByType(ExclusiveGateway.class);
        if (gateways.size() >= 1) {
            ExclusiveGateway last = gateways.get(gateways.size() - 1);
            SequenceFlow sequenceFlow = DrawUtils.createSequenceFlow(process, last, endEvent);
            // 设置顺序流条件(复核不通过)
            ConditionExpression conditionExpression = modelInstance.newInstance(ConditionExpression.class);
            conditionExpression.setTextContent("#{confirm==false}");
            sequenceFlow.setConditionExpression(conditionExpression);
        } else {
            //如果没有网关
            throw new RuntimeException("网关构建失败！");
        }
        // 必须【先】将复核和网关相连，之后再才能将所有的网关指向修改节点，否则流程图diagram会变得很奇怪（实测）
        for (ExclusiveGateway gateway : gateways) {
            SequenceFlow sequenceFlow = DrawUtils.createSequenceFlow(process, gateway, modifyNode);
            // 设置顺序流条件(复核不通过)
            ConditionExpression conditionExpression = modelInstance.newInstance(ConditionExpression.class);
            conditionExpression.setTextContent("#{confirm==false}");
            sequenceFlow.setConditionExpression(conditionExpression);
        }
        DrawUtils.createSequenceFlow(process, modifyNode, review1DTO);
        //
        // 读取文档以进行Xpath搜索
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(new InputSource(new ByteArrayInputStream(Bpmn.convertToString(modelInstance).getBytes(StandardCharsets.UTF_8))));
        doc.getDocumentElement().normalize();
        // 对于图，需要创建一个图和一个平面元素。平面被设置在一个图对象中，图被添加为子元素
        BpmnDiagram bpmnDiagram = modelInstance.newInstance(BpmnDiagram.class);
        BpmnPlane plane = modelInstance.newInstance(BpmnPlane.class);
        bpmnDiagram.setBpmnPlane(plane);
        definitions.addChildElement(bpmnDiagram);
        // process
        plane.setBpmnElement(process);
        // 创建每个形状的起点和终点的x和y坐标的散列映射，以便稍后作为序列流的引用。
        // 当我们添加形状时，我们将使用每个元素的id作为键并保存坐标
        HashMap<String, SequenceReferencePoints> refPoints = new HashMap<>();
        // 形状源引用的数组，用于按相对顺序放置进程形状
        ArrayList<String> sourceRefs = new ArrayList<>();
        ArrayList<String> nextSourceRefs = new ArrayList<>();
        // 要搜索和保存引用和坐标的对象
        XPath xpath = XPathFactory.newInstance().newXPath();
        // 获取开始事件
        XPathExpression searchRequest = xpath.compile("//*[contains(name(),'startEvent')]");
        NodeList eventNodes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);

        int x = 0;
        // 通过绘制开始事件开始图表
        for (int i = 0; i < eventNodes.getLength(); i++) {
            Element eElement = (Element) eventNodes.item(i);
            BpmnModelElementInstance element = modelInstance.getModelElementById(eElement.getAttribute("id"));
            // startEvent的(x,y)==(200,200)
            DrawUtils.drawShape(plane, modelInstance, element, 200, 200, 36, 36, false);
            refPoints.put(eElement.getAttribute("id"), new SequenceReferencePoints(
                    200, 220,
                    236, 220,
                    218, 202,
                    218, 238,
                    200, 220,
                    236, 220,
                    218, 202,
                    218, 238
            ));
            sourceRefs.add(eElement.getAttribute("id"));
        }
        // 开始节点与下一个节点之间的距离，不能删除
        x += 150;
        // 绘制下一个形状
        while (sourceRefs.size() > 0) {
            // 横向移动150像素以绘制下一组形状
            x += 150;
            // y将确定形状放置的y轴，并在每次运行开始时设置为零
            int yOffset = 0;

            for (String sourceRef : sourceRefs) {
                searchRequest = xpath.compile("//*[@sourceRef='" + sourceRef + "']");
                NodeList nextShapes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);

                for (int y = 0; y < nextShapes.getLength(); y++) {
                    Element tElement = (Element) nextShapes.item(y);
                    xpath = XPathFactory.newInstance().newXPath();
                    searchRequest = xpath.compile("//*[@id='" + tElement.getAttribute("targetRef") + "']");
                    NodeList shapes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);

                    for (int z = 0; z < shapes.getLength(); z++) {
                        Element sElement = (Element) shapes.item(z);
                        if (!refPoints.containsKey(sElement.getAttribute("id"))) {
                            nextSourceRefs.add(sElement.getAttribute("id"));

                            String type = sElement.getNodeName();

                            switch (type) {
                                case ("userTask"):
                                case ("bpmn:userTask"):
                                case ("task"):
                                case ("bpmn:task"):
                                    BpmnModelElementInstance element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x, (180 + yOffset) + y * 200, 80, 100, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(
                                            x, (220 + yOffset) + y * 200,//左入口
                                            x + 100, (220 + yOffset) + y * 200,//右入口
                                            x + 50, ((220 + yOffset) + y * 200) - 40,//上入口
                                            x + 50, ((220 + yOffset) + y * 200) + 40,//下入口
                                            x, (220 + yOffset) + y * 200,//左出口
                                            x + 100, (220 + yOffset) + y * 200,//右出口
                                            x + 50, ((220 + yOffset) + y * 200) - 40,//上出口
                                            x + 50, ((220 + yOffset) + y * 200) + 40//下出口
                                    ));
                                    break;
                                case ("exclusiveGateway"):
                                case ("bpmn:exclusiveGateway"):
                                case ("inclusiveGateway"):
                                case ("bpmn:inclusiveGateway"):
                                case ("parallelGateway"):
                                case ("bpmn:parallelGateway"):
                                case ("eventBasedGateway"):
                                case ("bpmn:eventBasedGateway"):
                                    element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x + 25, ((195 + yOffset) + y * 200), 50, 50, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(
                                            x + 25, (220 + yOffset) + y * 200,//左入口
                                            x + 75, (220 + yOffset) + y * 200,//右入口
                                            x + 50, ((220 + yOffset) + y * 200) - 25,//上入口
                                            x + 50, ((220 + yOffset) + y * 200) + 25,//下入口
                                            x + 25, (220 + yOffset) + y * 200,//左出口
                                            x + 75, (220 + yOffset) + y * 200,//右出口
                                            x + 50, ((220 + yOffset) + y * 200) - 25,//上出口
                                            x + 50, ((220 + yOffset) + y * 200) + 25//下出口
                                    ));
                                    break;
                                case ("endEvent"):
                                case ("bpmn:endEvent"):
                                    element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x, ((200 + yOffset) + y * 200), 36, 36, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(
                                            x, (220 + yOffset) + y * 200,
                                            x + 36, (220 + yOffset) + y * 200,
                                            x + 18, (220 + yOffset) + y * 200 - 18,
                                            x + 18, (220 + yOffset) + y * 200 + 18,
                                            x, (220 + yOffset) + y * 200,
                                            x + 36, (220 + yOffset) + y * 200,
                                            x + 18, (220 + yOffset) + y * 200 - 18,
                                            x + 18, (220 + yOffset) + y * 200 + 18
                                    ));
                                    break;
                                case ("textAnnotation"):
                                case ("bpmn:textAnnotation"):
                                    element = modelInstance.getModelElementById(sElement.getAttribute("id"));
                                    DrawUtils.drawShape(plane, modelInstance, element, x, ((200 + yOffset) + y * 80), 200, 200, false);
                                    refPoints.put(sElement.getAttribute("id"), new SequenceReferencePoints(x, ((220 + yOffset) + y * 80), (x + 36), ((220 + yOffset) + y * 80)));
                                    break;
                            }
                        }
                    }
                }
            }
            sourceRefs.clear();
            sourceRefs.addAll(nextSourceRefs);
            nextSourceRefs.clear();
        }
        // 找到并绘制序列流，现在形状已经绘制，序列流的参考点已经建立
        searchRequest = xpath.compile("//*[contains(name(),'sequenceFlow')]");
        NodeList sfNodes = (NodeList) searchRequest.evaluate(doc, XPathConstants.NODESET);
        for (int i = 0; i < sfNodes.getLength(); i++) {
            Element sfElement = (Element) sfNodes.item(i);
            DrawUtils.drawFlow(plane, modelInstance, sfElement, refPoints);
        }
        // 验证（官方方法）（实测的时候发现好像几乎没什么用处）
        Bpmn.validateModel(modelInstance);
        // 最后一步：部署
        repositoryService.createDeployment()
                .addModelInstance(processDefinitionName + ".bpmn20.xml", modelInstance)
                .name(processDefinitionName)
                .deploy();
        // 将建好的流程定义输出为格式为[xxx.bpmn20.xml]的文件
//         Bpmn.writeModelToFile(new File("D:\\"+processDefinitionName+"bpmn20.xml"), modelInstance);
        return true;
    }

}
*/
